//
//  WMPageController.m
//  WMPageController
//
//  Created by Mark on 15/6/11.
//  Copyright (c) 2015年 yq. All rights reserved.
//

#import "WMPageController.h"

NSString *const WMControllerDidAddToSuperViewNotification = @"WMControllerDidAddToSuperViewNotification";
NSString *const WMControllerDidFullyDisplayedNotification = @"WMControllerDidFullyDisplayedNotification";

static NSInteger const kWMUndefinedIndex = -1;
static NSInteger const kWMControllerCountUndefined = -1;
@interface WMPageController () {
        CGFloat _targetX;
        CGRect  _contentViewFrame, _menuViewFrame;
        BOOL    _hasInited, _shouldNotScroll;
        NSInteger _initializedIndex, _controllerCount, _markedSelectIndex;
}
@property (nonatomic, strong, readwrite) UIViewController *currentViewController;
// 用于记录子控制器view的frame，用于 scrollView 上的展示的位置
@property (nonatomic, strong) NSMutableArray *childViewFrames;
// 当前展示在屏幕上的控制器，方便在滚动的时候读取 (避免不必要计算)
@property (nonatomic, strong) NSMutableDictionary *displayVC;
// 用于记录销毁的viewController的位置 (如果它是某一种scrollView的Controller的话)
@property (nonatomic, strong) NSMutableDictionary *posRecords;
// 用于缓存加载过的控制器
@property (nonatomic, strong) NSCache *memCache;
@property (nonatomic, strong) NSMutableDictionary *backgroundCache;
// 收到内存警告的次数
@property (nonatomic, assign) int memoryWarningCount;
@property (nonatomic, readonly) NSInteger childControllersCount;
@end

@implementation WMPageController

#pragma mark - Lazy Loading
- (NSMutableDictionary *)posRecords {
        if (_posRecords == nil) {
                _posRecords = [[NSMutableDictionary alloc] init];
        }
        return _posRecords;
}

- (NSMutableDictionary *)displayVC {
        if (_displayVC == nil) {
                _displayVC = [[NSMutableDictionary alloc] init];
        }
        return _displayVC;
}

- (NSMutableDictionary *)backgroundCache {
        if (_backgroundCache == nil) {
                _backgroundCache = [[NSMutableDictionary alloc] init];
        }
        return _backgroundCache;
}

#pragma mark - Public Methods

- (instancetype)initWithViewControllerClasses:(NSArray<Class> *)classes andTheirTitles:(NSArray<NSString *> *)titles {
        if (self = [super init]) {
                NSParameterAssert(classes.count == titles.count);
                _viewControllerClasses = [NSArray arrayWithArray:classes];
                _titles = [NSArray arrayWithArray:titles];
                
                [self wm_setup];
        }
        return self;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
        if (self = [super initWithCoder:aDecoder]) {
                [self wm_setup];
        }
        return self;
}

- (instancetype)init {
        if (self = [super init]) {
                [self wm_setup];
        }
        return self;
}

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
        if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
                [self wm_setup];
        }
        return self;
}

- (void)forceLayoutSubviews {
        if (!self.childControllersCount) return;
        // 计算宽高及子控制器的视图frame
        [self wm_calculateSize];
        [self wm_adjustScrollViewFrame];
        [self wm_adjustMenuViewFrame];
        [self wm_adjustDisplayingViewControllersFrame];
}

- (void)setScrollEnable:(BOOL)scrollEnable {
        _scrollEnable = scrollEnable;
        
        if (!self.scrollView) return;
        self.scrollView.scrollEnabled = scrollEnable;
}

- (void)setProgressViewCornerRadius:(CGFloat)progressViewCornerRadius {
        _progressViewCornerRadius = progressViewCornerRadius;
        if (self.menuView) {
                self.menuView.progressViewCornerRadius = progressViewCornerRadius;
        }
}

- (void)setMenuViewLayoutMode:(WMMenuViewLayoutMode)menuViewLayoutMode {
        _menuViewLayoutMode = menuViewLayoutMode;
        if (self.menuView.superview) {
                [self wm_resetMenuView];
        }
}

- (void)setCachePolicy:(WMPageControllerCachePolicy)cachePolicy {
        _cachePolicy = cachePolicy;
        if (cachePolicy != WMPageControllerCachePolicyDisabled) {
                self.memCache.countLimit = _cachePolicy;
        }
}

- (void)setSelectIndex:(int)selectIndex {
        _selectIndex = selectIndex;
        _markedSelectIndex = kWMUndefinedIndex;
        if (self.menuView && _hasInited) {
                [self.menuView selectItemAtIndex:selectIndex];
        } else {
                _markedSelectIndex = selectIndex;
        }
}

- (void)setProgressViewIsNaughty:(BOOL)progressViewIsNaughty {
        _progressViewIsNaughty = progressViewIsNaughty;
        if (self.menuView) {
                self.menuView.progressViewIsNaughty = progressViewIsNaughty;
        }
}

- (void)setProgressWidth:(CGFloat)progressWidth {
        _progressWidth = progressWidth;
        self.progressViewWidths = ({
                NSMutableArray *tmp = [NSMutableArray array];
                for (int i = 0; i < self.childControllersCount; i++) {
                        [tmp addObject:@(progressWidth)];
                }
                tmp.copy;
        });
}

- (void)setProgressViewWidths:(NSArray *)progressViewWidths {
        _progressViewWidths = progressViewWidths;
        if (self.menuView) {
                self.menuView.progressWidths = progressViewWidths;
        }
}

- (void)setMenuViewContentMargin:(CGFloat)menuViewContentMargin {
        _menuViewContentMargin = menuViewContentMargin;
        if (self.menuView) {
                self.menuView.contentMargin = menuViewContentMargin;
        }
}

- (void)reloadData {
        [self wm_clearDatas];
        
        if (!self.childControllersCount) return;
        
        [self wm_resetScrollView];
        [self.memCache removeAllObjects];
        [self wm_resetMenuView];
        [self viewDidLayoutSubviews];
        [self didEnterController:self.currentViewController atIndex:self.selectIndex];
}

- (void)updateTitle:(NSString *)title atIndex:(NSInteger)index {
        [self.menuView updateTitle:title atIndex:index andWidth:NO];
}

- (void)updateAttributeTitle:(NSAttributedString * _Nonnull)title atIndex:(NSInteger)index {
        [self.menuView updateAttributeTitle:title atIndex:index andWidth:NO];
}

- (void)updateTitle:(NSString *)title andWidth:(CGFloat)width atIndex:(NSInteger)index {
        if (self.itemsWidths && index < self.itemsWidths.count) {
                NSMutableArray *mutableWidths = [NSMutableArray arrayWithArray:self.itemsWidths];
                mutableWidths[index] = @(width);
                self.itemsWidths = [mutableWidths copy];
        } else {
                NSMutableArray *mutableWidths = [NSMutableArray array];
                for (int i = 0; i < self.childControllersCount; i++) {
                        CGFloat itemWidth = (i == index) ? width : self.menuItemWidth;
                        [mutableWidths addObject:@(itemWidth)];
                }
                self.itemsWidths = [mutableWidths copy];
        }
        [self.menuView updateTitle:title atIndex:index andWidth:YES];
}
- (void)setDirection:(WMPageControllerScrollDirection)direction
{
        if (_direction == direction) {
                return;
        }
        _direction = direction;
        [self forceLayoutSubviews];
}
- (void)setShowMenuView:(BOOL)showMenuView
{
        if (_showMenuView == showMenuView) {
                return;
        }
        _showMenuView = showMenuView;
        [self forceLayoutSubviews];
}
- (void)setShowOnNavigationBar:(BOOL)showOnNavigationBar {
        if (_showOnNavigationBar == showOnNavigationBar) {
                return;
        }
        
        _showOnNavigationBar = showOnNavigationBar;
        if (self.menuView) {
                [self.menuView removeFromSuperview];
                [self wm_addMenuView];
                [self forceLayoutSubviews];
                [self.menuView slideMenuAtProgress:self.selectIndex];
        }
}

#pragma mark - Notification
- (void)willResignActive:(NSNotification *)notification {
        for (int i = 0; i < self.childControllersCount; i++) {
                id obj = [self.memCache objectForKey:@(i)];
                if (obj) {
                        [self.backgroundCache setObject:obj forKey:@(i)];
                }
        }
}

- (void)willEnterForeground:(NSNotification *)notification {
        for (NSNumber *key in self.backgroundCache.allKeys) {
                if (![self.memCache objectForKey:key]) {
                        [self.memCache setObject:self.backgroundCache[key] forKey:key];
                }
        }
        [self.backgroundCache removeAllObjects];
}

#pragma mark - Delegate
- (NSDictionary *)infoWithIndex:(NSInteger)index {
        NSString *title = [self titleAtIndex:index];
        return @{@"title": title ?: @"", @"index": @(index)};
}

- (void)willCachedController:(UIViewController *)vc atIndex:(NSInteger)index {
        if (self.childControllersCount && [self.delegate respondsToSelector:@selector(pageController:willCachedViewController:withInfo:)]) {
                NSDictionary *info = [self infoWithIndex:index];
                [self.delegate pageController:self willCachedViewController:vc withInfo:info];
        }
}

- (void)willEnterController:(UIViewController *)vc atIndex:(NSInteger)index {
        _selectIndex = (int)index;
        if (self.childControllersCount && [self.delegate respondsToSelector:@selector(pageController:willEnterViewController:withInfo:)]) {
                NSDictionary *info = [self infoWithIndex:index];
                [self.delegate pageController:self willEnterViewController:vc withInfo:info];
        }
}

// 完全进入控制器 (即停止滑动后调用)
- (void)didEnterController:(UIViewController *)vc atIndex:(NSInteger)index {
        if (!self.childControllersCount) return;
        
        // Post FullyDisplayedNotification
        [self wm_postFullyDisplayedNotificationWithCurrentIndex:self.selectIndex];
        
        NSDictionary *info = [self infoWithIndex:index];
        if ([self.delegate respondsToSelector:@selector(pageController:didEnterViewController:withInfo:)]) {
                [self.delegate pageController:self didEnterViewController:vc withInfo:info];
        }
        
        // 当控制器创建时，调用延迟加载的代理方法
        if (_initializedIndex == index && [self.delegate respondsToSelector:@selector(pageController:lazyLoadViewController:withInfo:)]) {
                [self.delegate pageController:self lazyLoadViewController:vc withInfo:info];
                _initializedIndex = kWMUndefinedIndex;
        }
        
        // 根据 preloadPolicy 预加载控制器
        if (self.preloadPolicy == WMPageControllerPreloadPolicyNever) return;
        int length = (int)self.preloadPolicy;
        int start = 0;
        int end = (int)self.childControllersCount - 1;
        if (index > length) {
                start = (int)index - length;
        }
        if (self.childControllersCount - 1 > length + index) {
                end = (int)index + length;
        }
        for (int i = start; i <= end; i++) {
                // 如果已存在，不需要预加载
                if (![self.memCache objectForKey:@(i)] && !self.displayVC[@(i)]) {
                        [self wm_addViewControllerAtIndex:i];
                        [self wm_postAddToSuperViewNotificationWithIndex:i];
                }
        }
        _selectIndex = (int)index;
}

#pragma mark - Data source
- (NSInteger)childControllersCount {
        if (_controllerCount == kWMControllerCountUndefined) {
                if ([self.dataSource respondsToSelector:@selector(numbersOfChildControllersInPageController:)]) {
                        _controllerCount = [self.dataSource numbersOfChildControllersInPageController:self];
                } else {
                        _controllerCount = self.viewControllerClasses.count;
                }
        }
        return _controllerCount;
}

- (UIViewController * _Nonnull)initializeViewControllerAtIndex:(NSInteger)index {
        if ([self.dataSource respondsToSelector:@selector(pageController:viewControllerAtIndex:)]) {
                return [self.dataSource pageController:self viewControllerAtIndex:index];
        }
        return [[self.viewControllerClasses[index] alloc] init];
}

- (NSString * _Nonnull)titleAtIndex:(NSInteger)index {
        NSString *title = nil;
        if ([self.dataSource respondsToSelector:@selector(pageController:titleAtIndex:)]) {
                title = [self.dataSource pageController:self titleAtIndex:index];
        } else {
                title = self.titles[index];
        }
        return (title ?: @"");
}

#pragma mark - Private Methods

- (void)wm_resetScrollView {
        if (self.scrollView) {
                [self.scrollView removeFromSuperview];
        }
        [self wm_addScrollView];
        [self wm_addViewControllerAtIndex:self.selectIndex];
        self.currentViewController = self.displayVC[@(self.selectIndex)];
}

- (void)wm_clearDatas {
        _controllerCount = kWMControllerCountUndefined;
        _hasInited = NO;
        NSUInteger maxIndex = (self.childControllersCount - 1 > 0) ? (self.childControllersCount - 1) : 0;
        _selectIndex = self.selectIndex < self.childControllersCount ? self.selectIndex : (int)maxIndex;
        if (self.progressWidth > 0) { self.progressWidth = self.progressWidth; }
        
        NSArray *displayingViewControllers = self.displayVC.allValues;
        for (UIViewController *vc in displayingViewControllers) {
                [vc.view removeFromSuperview];
                [vc willMoveToParentViewController:nil];
                [vc removeFromParentViewController];
        }
        self.memoryWarningCount = 0;
        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(wm_growCachePolicyAfterMemoryWarning) object:nil];
        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(wm_growCachePolicyToHigh) object:nil];
        self.currentViewController = nil;
        [self.posRecords removeAllObjects];
        [self.displayVC removeAllObjects];
}

// 当子控制器init完成时发送通知
- (void)wm_postAddToSuperViewNotificationWithIndex:(int)index {
        if (!self.postNotification) return;
        NSDictionary *info = @{
                               @"index":@(index),
                               @"title":[self titleAtIndex:index]
                               };
        [[NSNotificationCenter defaultCenter] postNotificationName:WMControllerDidAddToSuperViewNotification
                                                            object:self
                                                          userInfo:info];
}

// 当子控制器完全展示在user面前时发送通知
- (void)wm_postFullyDisplayedNotificationWithCurrentIndex:(int)index {
        if (!self.postNotification) return;
        NSDictionary *info = @{
                               @"index":@(index),
                               @"title":[self titleAtIndex:index]
                               };
        [[NSNotificationCenter defaultCenter] postNotificationName:WMControllerDidFullyDisplayedNotification
                                                            object:self
                                                          userInfo:info];
}

// 初始化一些参数，在init中调用
- (void)wm_setup {
        _titleSizeSelected  = 18.0f;
        _titleSizeNormal    = 15.0f;
        _titleColorSelected = [UIColor colorWithRed:168.0/255.0 green:20.0/255.0 blue:4/255.0 alpha:1];
        _titleColorNormal   = [UIColor colorWithRed:0 green:0 blue:0 alpha:1];
        _menuItemWidth = 65.0f;
        
        _memCache = [[NSCache alloc] init];
        _initializedIndex = kWMUndefinedIndex;
        _markedSelectIndex = kWMUndefinedIndex;
        _controllerCount  = kWMControllerCountUndefined;
        _scrollEnable = YES;
        _showMenuView = YES;
        self.automaticallyCalculatesItemWidths = NO;
        if ( @available(iOS 11.0, *) )
        {
                self.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
        }
        else
        {
                self.automaticallyAdjustsScrollViewInsets = NO;
        }
        
        self.preloadPolicy = WMPageControllerPreloadPolicyNever;
        self.cachePolicy = WMPageControllerCachePolicyNoLimit;
        
        self.delegate = self;
        self.dataSource = self;
        
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
}

// 包括宽高，子控制器视图 frame
- (void)wm_calculateSize {
        if (self.showMenuView) {
                if ([self.dataSource respondsToSelector:@selector(pageController:preferredFrameForMenuView:)]) {
                        _menuViewFrame = [self.dataSource pageController:self preferredFrameForMenuView:self.menuView];
                } else {
                        CGFloat originY = (self.showOnNavigationBar && self.navigationController.navigationBar) ? 0 : CGRectGetMaxY(self.navigationController.navigationBar.frame);
                        _menuViewFrame = CGRectMake(0, originY, self.view.frame.size.width, 30.0f);
                }
        } else {
                _menuViewFrame = CGRectMake(0, 0, self.view.frame.size.width, 0.0f);
        }
        
        if ([self.dataSource respondsToSelector:@selector(pageController:preferredFrameForContentView:)]) {
                _contentViewFrame = [self.dataSource pageController:self preferredFrameForContentView:self.scrollView];
        } else {
                CGFloat originY = (self.showOnNavigationBar && self.navigationController.navigationBar) ? CGRectGetMaxY(self.navigationController.navigationBar.frame) : CGRectGetMaxY(_menuViewFrame);
                CGFloat tabBarHeight = self.tabBarController.tabBar && !self.tabBarController.tabBar.hidden ? self.tabBarController.tabBar.frame.size.height : 0;
                CGFloat sizeHeight = self.view.frame.size.height - tabBarHeight - originY;
                _contentViewFrame = CGRectMake(0, originY, self.view.frame.size.width, sizeHeight);
        }
        
        _childViewFrames = [NSMutableArray array];
        if (self.direction == WMPageControllerScrollDirectionHorizontal) {
                for (int i = 0; i < self.childControllersCount; i++) {
                        CGRect frame = CGRectMake(i * _contentViewFrame.size.width, 0, _contentViewFrame.size.width, _contentViewFrame.size.height);
                        [_childViewFrames addObject:[NSValue valueWithCGRect:frame]];
                }
        }
        else if (self.direction == WMPageControllerScrollDirectionVertical) {
                for (int i = 0; i < self.childControllersCount; i++) {
                        CGRect frame = CGRectMake(0, i * _contentViewFrame.size.height, _contentViewFrame.size.width, _contentViewFrame.size.height);
                        [_childViewFrames addObject:[NSValue valueWithCGRect:frame]];
                }
        }
}

- (void)wm_addScrollView {
        WMScrollView *scrollView = [[WMScrollView alloc] init];
        scrollView.scrollsToTop = NO;
        scrollView.pagingEnabled = YES;
        scrollView.backgroundColor = [UIColor whiteColor];
        scrollView.delegate = self;
        scrollView.showsVerticalScrollIndicator = NO;
        scrollView.showsHorizontalScrollIndicator = NO;
        scrollView.bounces = self.bounces;
        scrollView.scrollEnabled = self.scrollEnable;
        [self.view addSubview:scrollView];
        self.scrollView = scrollView;
        
        if (@available(iOS 11.0, *)) {
                self.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
        } else {
                // Fallback on earlier versions
                self.automaticallyAdjustsScrollViewInsets = NO;
        }
        if (!self.navigationController) return;
        for (UIGestureRecognizer *gestureRecognizer in scrollView.gestureRecognizers) {
                [gestureRecognizer requireGestureRecognizerToFail:self.navigationController.interactivePopGestureRecognizer];
        }
}

- (void)wm_addMenuView {
        WMMenuView *menuView = [[WMMenuView alloc] initWithFrame:CGRectZero];
        menuView.delegate = self;
        menuView.dataSource = self;
        menuView.style = self.menuViewStyle;
        menuView.layoutMode = self.menuViewLayoutMode;
        menuView.progressHeight = self.progressHeight;
        menuView.contentMargin = self.menuViewContentMargin;
        menuView.progressViewBottomSpace = self.progressViewBottomSpace;
        menuView.progressWidths = self.progressViewWidths;
        menuView.progressViewIsNaughty = self.progressViewIsNaughty;
        menuView.progressViewCornerRadius = self.progressViewCornerRadius;
        if (self.titleFontName) {
                menuView.fontName = self.titleFontName;
        }
        if (self.progressColor) {
                menuView.lineColor = self.progressColor;
        }
        if (self.showOnNavigationBar && self.navigationController.navigationBar) {
                self.navigationItem.titleView = menuView;
        } else {
                [self.view addSubview:menuView];
        }
        self.menuView = menuView;
}

- (void)wm_layoutChildViewControllers {
        int currentPage = 0;
        if (self.direction == WMPageControllerScrollDirectionHorizontal) {
                currentPage = (int)(self.scrollView.contentOffset.x / _contentViewFrame.size.width);
        } else if (self.direction == WMPageControllerScrollDirectionVertical) {
                currentPage = (int)(self.scrollView.contentOffset.y / _contentViewFrame.size.height);
        }
        int length = (int)self.preloadPolicy;
        int left = currentPage - length - 1;
        int right = currentPage + length + 1;
        for (int i = 0; i < self.childControllersCount; i++) {
                UIViewController *vc = [self.displayVC objectForKey:@(i)];
                CGRect frame = [self.childViewFrames[i] CGRectValue];
                if (!vc) {
                        if ([self wm_isInScreen:frame]) {
                                [self wm_initializedControllerWithIndexIfNeeded:i];
                        }
                } else if (i <= left || i >= right) {
                        if (![self wm_isInScreen:frame]) {
                                [self wm_removeViewController:vc atIndex:i];
                        }
                }
        }
}

// 创建或从缓存中获取控制器并添加到视图上
- (void)wm_initializedControllerWithIndexIfNeeded:(NSInteger)index {
        // 先从 cache 中取
        UIViewController *vc = [self.memCache objectForKey:@(index)];
        if (vc) {
                // cache 中存在，添加到 scrollView 上，并放入display
                [self wm_addCachedViewController:vc atIndex:index];
        } else {
                // cache 中也不存在，创建并添加到display
                [self wm_addViewControllerAtIndex:(int)index];
        }
        [self wm_postAddToSuperViewNotificationWithIndex:(int)index];
}

- (void)wm_addCachedViewController:(UIViewController *)viewController atIndex:(NSInteger)index {
        [self addChildViewController:viewController];
        viewController.view.frame = [self.childViewFrames[index] CGRectValue];
        [viewController didMoveToParentViewController:self];
        [self.scrollView addSubview:viewController.view];
        [self willEnterController:viewController atIndex:index];
        [self.displayVC setObject:viewController forKey:@(index)];
}

// 创建并添加子控制器
- (void)wm_addViewControllerAtIndex:(int)index {
        _initializedIndex = index;
        UIViewController *viewController = [self initializeViewControllerAtIndex:index];
        if (self.values.count == self.childControllersCount && self.keys.count == self.childControllersCount) {
                [viewController setValue:self.values[index] forKey:self.keys[index]];
        }
        [self addChildViewController:viewController];
        CGRect frame = self.childViewFrames.count ? [self.childViewFrames[index] CGRectValue] : self.view.frame;
        viewController.view.frame = frame;
        [viewController didMoveToParentViewController:self];
        [self.scrollView addSubview:viewController.view];
        [self willEnterController:viewController atIndex:index];
        [self.displayVC setObject:viewController forKey:@(index)];
        
        [self wm_backToPositionIfNeeded:viewController atIndex:index];
}

// 移除控制器，且从display中移除
- (void)wm_removeViewController:(UIViewController *)viewController atIndex:(NSInteger)index {
        [self wm_rememberPositionIfNeeded:viewController atIndex:index];
        [viewController.view removeFromSuperview];
        [viewController willMoveToParentViewController:nil];
        [viewController removeFromParentViewController];
        [self.displayVC removeObjectForKey:@(index)];
        
        // 放入缓存
        if (self.cachePolicy == WMPageControllerCachePolicyDisabled) {
                return;
        }
        
        if (![self.memCache objectForKey:@(index)]) {
                [self willCachedController:viewController atIndex:index];
                [self.memCache setObject:viewController forKey:@(index)];
        }
}

- (void)wm_backToPositionIfNeeded:(UIViewController *)controller atIndex:(NSInteger)index {
#pragma clang diagnostic push
#pragma clang diagnostic ignored"-Wdeprecated-declarations"
        if (!self.rememberLocation) return;
#pragma clang diagnostic pop
        if ([self.memCache objectForKey:@(index)]) return;
        UIScrollView *scrollView = [self wm_isKindOfScrollViewController:controller];
        if (scrollView) {
                NSValue *pointValue = self.posRecords[@(index)];
                if (pointValue) {
                        CGPoint pos = [pointValue CGPointValue];
                        [scrollView setContentOffset:pos];
                }
        }
}

- (void)wm_rememberPositionIfNeeded:(UIViewController *)controller atIndex:(NSInteger)index {
#pragma clang diagnostic push
#pragma clang diagnostic ignored"-Wdeprecated-declarations"
        if (!self.rememberLocation) return;
#pragma clang diagnostic pop
        UIScrollView *scrollView = [self wm_isKindOfScrollViewController:controller];
        if (scrollView) {
                CGPoint pos = scrollView.contentOffset;
                self.posRecords[@(index)] = [NSValue valueWithCGPoint:pos];
        }
}

- (UIScrollView *)wm_isKindOfScrollViewController:(UIViewController *)controller {
        UIScrollView *scrollView = nil;
        if ([controller.view isKindOfClass:[UIScrollView class]]) {
                // Controller的view是scrollView的子类(UITableViewController/UIViewController替换view为scrollView)
                scrollView = (UIScrollView *)controller.view;
        } else if (controller.view.subviews.count >= 1) {
                // Controller的view的subViews[0]存在且是scrollView的子类，并且frame等与view得frame(UICollectionViewController/UIViewController添加UIScrollView)
                UIView *view = controller.view.subviews[0];
                if ([view isKindOfClass:[UIScrollView class]]) {
                        scrollView = (UIScrollView *)view;
                }
        }
        return scrollView;
}

- (BOOL)wm_isInScreen:(CGRect)frame {
        if (self.direction == WMPageControllerScrollDirectionHorizontal) {
                
                CGFloat x = frame.origin.x;
                CGFloat screenWidth = self.scrollView.frame.size.width;
                
                CGFloat contentOffsetX = self.scrollView.contentOffset.x;
                if (CGRectGetMaxX(frame) > contentOffsetX && x - contentOffsetX < screenWidth) {
                        return YES;
                } else {
                        return NO;
                }
        } else if (self.direction == WMPageControllerScrollDirectionVertical) {
                CGFloat x = frame.origin.y;
                CGFloat screenHeight = self.scrollView.frame.size.height;
                
                CGFloat contentOffsetY = self.scrollView.contentOffset.y;
                if (CGRectGetMaxY(frame) > contentOffsetY && x - contentOffsetY < screenHeight) {
                        return YES;
                } else {
                        return NO;
                }
        }
        return NO;
}

- (void)wm_resetMenuView {
        if (!self.menuView) {
                [self wm_addMenuView];
        } else {
                [self.menuView reload];
                if (self.menuView.userInteractionEnabled == NO) {
                        self.menuView.userInteractionEnabled = YES;
                }
                if (self.selectIndex != 0) {
                        [self.menuView selectItemAtIndex:self.selectIndex];
                }
                [self.view bringSubviewToFront:self.menuView];
        }
}

- (void)wm_growCachePolicyAfterMemoryWarning {
        self.cachePolicy = WMPageControllerCachePolicyBalanced;
        [self performSelector:@selector(wm_growCachePolicyToHigh) withObject:nil afterDelay:2.0 inModes:@[NSRunLoopCommonModes]];
}

- (void)wm_growCachePolicyToHigh {
        self.cachePolicy = WMPageControllerCachePolicyHigh;
}

- (UIView *)wm_bottomView {
        return self.tabBarController.tabBar ? self.tabBarController.tabBar : self.navigationController.toolbar;
}

#pragma mark - Adjust Frame
- (void)wm_adjustScrollViewFrame {
        // While rotate at last page, set scroll frame will call `-scrollViewDidScroll:` delegate
        // It's not my expectation, so I use `_shouldNotScroll` to lock it.
        // Wait for a better solution.
        _shouldNotScroll = YES;
        if (self.direction == WMPageControllerScrollDirectionHorizontal) {
                CGFloat oldContentOffsetX = self.scrollView.contentOffset.x;
                CGFloat contentWidth = self.scrollView.contentSize.width;
                self.scrollView.frame = _contentViewFrame;
                self.scrollView.contentSize = CGSizeMake(self.childControllersCount * _contentViewFrame.size.width, 0);
                CGFloat xContentOffset = contentWidth == 0 ? self.selectIndex * _contentViewFrame.size.width : oldContentOffsetX / contentWidth * self.childControllersCount * _contentViewFrame.size.width;
                [self.scrollView setContentOffset:CGPointMake(xContentOffset, 0)];
        } else if (self.direction == WMPageControllerScrollDirectionVertical) {
                CGFloat oldContentOffsetY = self.scrollView.contentOffset.y;
                CGFloat contentHeight = self.scrollView.contentSize.height;
                self.scrollView.frame = _contentViewFrame;
                self.scrollView.contentSize = CGSizeMake(0, self.childControllersCount * _contentViewFrame.size.height);
                CGFloat yContentOffset = contentHeight == 0 ? self.selectIndex * _contentViewFrame.size.height : oldContentOffsetY / contentHeight * self.childControllersCount * _contentViewFrame.size.height;
                [self.scrollView setContentOffset:CGPointMake(0, yContentOffset)];
        }
        _shouldNotScroll = NO;
}

- (void)wm_adjustDisplayingViewControllersFrame {
        [self.displayVC enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, UIViewController * _Nonnull vc, BOOL * _Nonnull stop) {
                NSInteger index = key.integerValue;
                CGRect frame = [self.childViewFrames[index] CGRectValue];
                vc.view.frame = frame;
        }];
}

- (void)wm_adjustMenuViewFrame {
        if (self.showMenuView)
        {
                CGFloat oriWidth = self.menuView.frame.size.width;
                self.menuView.frame = _menuViewFrame;
                [self.menuView resetFrames];
                if (oriWidth != self.menuView.frame.size.width) {
                        [self.menuView refreshContenOffset];
                }
        }
        else
        {
                self.menuView.frame = CGRectMake(_menuViewFrame.origin.x, _menuViewFrame.origin.y, _menuViewFrame.size.width, 0);
        }
        
}

- (CGFloat)wm_calculateItemWithAtIndex:(NSInteger)index {
        NSString *title = [self titleAtIndex:index];
        UIFont *titleFont = self.titleFontName ? [UIFont fontWithName:self.titleFontName size:self.titleSizeSelected] : [UIFont systemFontOfSize:self.titleSizeSelected];
        NSDictionary *attrs = @{NSFontAttributeName: titleFont};
        CGFloat itemWidth = [title boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading) attributes:attrs context:nil].size.width;
        return ceil(itemWidth);
}

- (void)wm_delaySelectIndexIfNeeded {
        if (_markedSelectIndex != kWMUndefinedIndex) {
                self.selectIndex = (int)_markedSelectIndex;
        }
}

#pragma mark - Life Cycle
- (void)viewDidLoad {
        [super viewDidLoad];
        self.view.backgroundColor = [UIColor whiteColor];
        if (!self.childControllersCount) return;
        [self wm_calculateSize];
        [self wm_addScrollView];
        [self wm_addViewControllerAtIndex:self.selectIndex];
        self.currentViewController = self.displayVC[@(self.selectIndex)];
        [self wm_addMenuView];
        [self didEnterController:self.currentViewController atIndex:self.selectIndex];
}

- (void)viewDidLayoutSubviews {
        [super viewDidLayoutSubviews];
        
        if (!self.childControllersCount) return;
        [self forceLayoutSubviews];
        _hasInited = YES;
        [self wm_delaySelectIndexIfNeeded];
}


- (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
        self.memoryWarningCount++;
        self.cachePolicy = WMPageControllerCachePolicyLowMemory;
        // 取消正在增长的 cache 操作
        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(wm_growCachePolicyAfterMemoryWarning) object:nil];
        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(wm_growCachePolicyToHigh) object:nil];
        
        [self.memCache removeAllObjects];
        [self.posRecords removeAllObjects];
        self.posRecords = nil;
        
        // 如果收到内存警告次数小于 3，一段时间后切换到模式 Balanced
        if (self.memoryWarningCount < 3) {
                [self performSelector:@selector(wm_growCachePolicyAfterMemoryWarning) withObject:nil afterDelay:3.0 inModes:@[NSRunLoopCommonModes]];
        }
}

#pragma mark - UIScrollView Delegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
        if (![scrollView isKindOfClass:WMScrollView.class]) return;
        
        if (_shouldNotScroll || !_hasInited) return;
        
        [self wm_layoutChildViewControllers];
        if (_startDragging) {
                CGFloat rate = 0.0;
                if (self.direction == WMPageControllerScrollDirectionHorizontal) {
                        CGFloat contentOffsetX = scrollView.contentOffset.x;
                        if (contentOffsetX < 0) {
                                contentOffsetX = 0;
                        }
                        if (contentOffsetX > scrollView.contentSize.width - _contentViewFrame.size.width) {
                                contentOffsetX = scrollView.contentSize.width - _contentViewFrame.size.width;
                        }
                        rate = contentOffsetX / _contentViewFrame.size.width;
                        [self.menuView slideMenuAtProgress:rate];
                        // Fix scrollView.contentOffset.y -> (-20) unexpectedly.
                        if (scrollView.contentOffset.y == 0) return;
                        CGPoint contentOffset = scrollView.contentOffset;
                        contentOffset.y = 0.0;
                        scrollView.contentOffset = contentOffset;
                        
                } else if (self.direction == WMPageControllerScrollDirectionVertical) {
                        CGFloat contentOffsetY = scrollView.contentOffset.y;
                        if (contentOffsetY < 0) {
                                contentOffsetY = 0;
                        }
                        if (contentOffsetY > scrollView.contentSize.height - _contentViewFrame.size.height) {
                                contentOffsetY = scrollView.contentSize.height - _contentViewFrame.size.height;
                        }
                        rate = contentOffsetY / _contentViewFrame.size.height;
                        [self.menuView slideMenuAtProgress:rate];
                        // Fix scrollView.contentOffset.y -> (-20) unexpectedly.
                        if (scrollView.contentOffset.y == 0) return;
                        CGPoint contentOffset = scrollView.contentOffset;
                        contentOffset.x = 0.0;
                        scrollView.contentOffset = contentOffset;
                }
        }
        
        
}

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
        if (![scrollView isKindOfClass:WMScrollView.class]) return;
        
        _startDragging = YES;
        self.menuView.userInteractionEnabled = NO;
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
        if (![scrollView isKindOfClass:WMScrollView.class]) return;
        
        self.menuView.userInteractionEnabled = YES;
        if (self.direction == WMPageControllerScrollDirectionHorizontal) {
                _selectIndex = (int)(scrollView.contentOffset.x / _contentViewFrame.size.width);
        } else if (self.direction == WMPageControllerScrollDirectionVertical) {
                _selectIndex = (int)(scrollView.contentOffset.y / _contentViewFrame.size.height);
        }
        self.currentViewController = self.displayVC[@(self.selectIndex)];
        [self didEnterController:self.currentViewController atIndex:self.selectIndex];
        [self.menuView deselectedItemsIfNeeded];
}

- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
        if (![scrollView isKindOfClass:WMScrollView.class]) return;
        
        self.currentViewController = self.displayVC[@(self.selectIndex)];
        [self didEnterController:self.currentViewController atIndex:self.selectIndex];
        [self.menuView deselectedItemsIfNeeded];
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
        if (![scrollView isKindOfClass:WMScrollView.class]) return;
        
        if (!decelerate) {
                self.menuView.userInteractionEnabled = YES;
                CGFloat rate = _targetX / _contentViewFrame.size.width;
                [self.menuView slideMenuAtProgress:rate];
                [self.menuView deselectedItemsIfNeeded];
        }
}

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
        if (![scrollView isKindOfClass:WMScrollView.class]) return;
        
        _targetX = targetContentOffset->x;
}

#pragma mark - WMMenuView Delegate
- (void)menuView:(WMMenuView *)menu didSelesctedIndex:(NSInteger)index currentIndex:(NSInteger)currentIndex {
        if (!_hasInited) return;
        _selectIndex = (int)index;
        _startDragging = NO;
        if (self.direction == WMPageControllerScrollDirectionHorizontal) {
                CGPoint targetP = CGPointMake(_contentViewFrame.size.width * index, 0);
                [self.scrollView setContentOffset:targetP animated:self.pageAnimatable];
        } else if (self.direction == WMPageControllerScrollDirectionVertical) {
                CGPoint targetP = CGPointMake(0, _contentViewFrame.size.height * index);
                [self.scrollView setContentOffset:targetP animated:self.pageAnimatable];
        }
        
        if (self.pageAnimatable) return;
        // 由于不触发 -scrollViewDidScroll: 手动处理控制器
        UIViewController *currentViewController = self.displayVC[@(currentIndex)];
        if (currentViewController) {
                [self wm_removeViewController:currentViewController atIndex:currentIndex];
        }
        [self wm_layoutChildViewControllers];
        self.currentViewController = self.displayVC[@(self.selectIndex)];
        
        [self didEnterController:self.currentViewController atIndex:index];
}

- (CGFloat)menuView:(WMMenuView *)menu widthForItemAtIndex:(NSInteger)index {
        if (self.automaticallyCalculatesItemWidths) {
                return [self wm_calculateItemWithAtIndex:index];
        }
        
        if (self.itemsWidths.count == self.childControllersCount) {
                return [self.itemsWidths[index] floatValue];
        }
        return self.menuItemWidth;
}

- (CGFloat)menuView:(WMMenuView *)menu itemMarginAtIndex:(NSInteger)index {
        if (self.itemsMargins.count == self.childControllersCount + 1) {
                return [self.itemsMargins[index] floatValue];
        }
        return self.itemMargin;
}

- (CGFloat)menuView:(WMMenuView *)menu titleSizeForState:(WMMenuItemState)state atIndex:(NSInteger)index {
        switch (state) {
                case WMMenuItemStateSelected: {
                        return self.titleSizeSelected;
                        break;
                }
                case WMMenuItemStateNormal: {
                        return self.titleSizeNormal;
                        break;
                }
        }
}

- (UIColor *)menuView:(WMMenuView *)menu titleColorForState:(WMMenuItemState)state atIndex:(NSInteger)index {
        switch (state) {
                case WMMenuItemStateSelected: {
                        return self.titleColorSelected;
                        break;
                }
                case WMMenuItemStateNormal: {
                        return self.titleColorNormal;
                        break;
                }
        }
}

#pragma mark - WMMenuViewDataSource
- (NSInteger)numbersOfTitlesInMenuView:(WMMenuView *)menu {
        return self.childControllersCount;
}

- (NSString *)menuView:(WMMenuView *)menu titleAtIndex:(NSInteger)index {
        return [self titleAtIndex:index];
}

@end
// @{@"title": title ?: @"", @"index": @(index)};
@implementation NSDictionary (WMPageController)
- (NSUInteger)infoIndexValue
{
        id value = [self valueForKey:@"index"];
        if (!value || ![value isKindOfClass:[NSNumber class]]) {
                return 0;
        }
        return [value unsignedIntegerValue];
        
}
- (NSString *)infoTitleValue
{
        id value = [self valueForKey:@"title"];
        if (![value isKindOfClass:[NSString class]]) {
                return nil;
        }
        return (NSString *)value;
}


@end
